package service

import (
	"bytes"
	"encoding/json"
	"fmt"
	"github.com/wonderivan/logger"
	"io"
	"net/http"
	"strconv"
	"strings"
	"ucenter/app"
	"ucenter/app/dao"
	"ucenter/app/model"
)

var (
	orderProcessList map[int64]string
)

const (
	//支付网关定义状态
	PaySuccess       = "SUCCESS"
	PayFail          = "FAILED"
	PayIngress       = "PAING"
	TransInProgress  = "InProgress"
	TransSuccess     = "Success"
	TransUnConfirmed = "UnConfirmed"
	TransUnknown     = "Unknown"
	TransFail        = "Fail"
)

type PayChannel int8
type TradeType int8

type PayError struct {
	message string
	Code    int
}

func (e *PayError) Error() string {
	return e.message
}

const (
	OrderSystemError = 1000
	OrderIdErr       = 1001
	CashNotEnough    = 1002
	UserInfoError    = 1004
)

type PayStatusResp struct {
	Code string `json:"code"`
	Data *struct {
		PaymentId      string `json:"payment_id"`
		PaymentOrderId string `json:"payment_order_id"`
		Status         string `json:"SUCCESS"`
	} `json:"data"`
	Message string `json:"message"`
}
type PayNotifyBody struct {
	PayChannel    string `json:"pay_channel"`
	OrderId       string `json:"app_order_id"`
	TransactionNo string `json:"order_id"`
	Status        int    `json:"status"`
	Amount        int    `json:"amount"`
	UserId        string `json:"user_id"`
	Phone         string `json:"phone"`
	Email         string `json:"email"`
}

const (
	_ PayChannel = iota
	CashFree
	PayTm
	Bank
)

const (
	_ TradeType = iota
	In
	Out
)

func (s *service) PreOrder(uid int64, payChannel string, channelId string, tradeType TradeType, amount int, appId string) (payToken *PayToken, err error) {
	var (
		formData            map[string]interface{}
		orderId             int64
		createResp          *http.Response
		createOrderRespInfo map[string]interface{}
		userInfo            *model.User
		res                 string
	)
	payToken = &PayToken{}
	res, userInfo, _ = GetUserService().GetUserBaseInfo(uid)
	if res != "" {
		err = &PayError{
			message: "user error",
			Code:    UserInfoError,
		}
		return
	}
	orderId, err = s.getOrderId()
	if err != nil {
		logger.Error("uuid get fail , err:%s", err.Error())
		err = &PayError{
			message: "order id gateway fail",
			Code:    500,
		}
		return
	}
	formData = map[string]interface{}{}
	formData["app_id"] = appId
	formData["app_order_id"] = strconv.FormatInt(orderId, 10)
	formData["pay_channel"] = payChannel
	formData["user_id"] = strconv.FormatInt(uid, 10)
	formData["phone"] = userInfo.Phone
	formData["email"] = userInfo.Email
	formData["user_name"] = userInfo.NickName
	formData["amount"] = amount
	postRaw, _ := json.Marshal(formData)
	logger.Debug("post create order to payment, %+v", formData)
	createResp, err = s.payClient.Post(fmt.Sprintf("http://%s/v1/create_in_order", app.Conf.PayConf.Host), "application/json", bytes.NewBuffer(postRaw))
	defer createResp.Body.Close()
	if err != nil {
		logger.Error("create order fail | err:%s", err.Error())
		err = &PayError{
			message: "api gateway fail",
			Code:    OrderSystemError,
		}
		return
	}
	if createResp.StatusCode != http.StatusOK {
		var body []byte
		createResp.Body.Read(body)
		logger.Error("create order fail | pay gateway fail | code:%d | body: %s ", createResp.StatusCode, body)
		err = &PayError{
			message: "api gateway status exception",
			Code:    OrderSystemError,
		}
		return
	}

	if err = json.NewDecoder(io.Reader(createResp.Body)).Decode(&createOrderRespInfo); err != nil {
		logger.Error("create order create fail %s %s", createResp.Body, err.Error())
		err = &PayError{
			message: "api gateway response fail",
			Code:    OrderSystemError,
		}
		return
	}
	logger.Debug("pay gateway order success| body: %+v ", createResp.StatusCode, createOrderRespInfo)

	payToken.Channel = payChannel
	payToken.Attachment = ""
	payToken.AppOrderId = orderId
	if data, ok := createOrderRespInfo["data"]; ok {
		payToken.TransactionNo = data.(map[string]interface{})["order_id"].(string)
		payToken.Payload = []byte(data.(map[string]interface{})["payment_order_id"].(string))
	}
	//TODO write BD
	orderInfo := &dao.Transaction{
		Uid:           uid,
		AppId:         appId,
		OrderId:       orderId,
		TransactionNo: payToken.TransactionNo,
		Currency:      "INC",
		Amount:        amount,
		TradeChannel:  payChannel,
		DeviceId:      userInfo.DeviceId,
		Status:        "InProgress",
		Version:       1,
	}
	if tradeType == In {
		orderInfo.TradeType = "IN"
	} else {
		orderInfo.TradeType = "OUT"
	}
	err = s.dao.NewOrder(orderInfo)
	if err != nil {
		logger.Error("insert transaction fail, %s", err.Error())
	}
	if err != nil {
		return
	}
	//TODO 提现订单待完善
	if tradeType == Out {
		_, err = GetBalanceService().BalanceNormal(&model.BalanceRequest{
			Wins:         0,
			UidS:         []int64{uid},
			RoomType:     0,
			RoomId:       "",
			Op:           model.Op_Dim,
			CashNumInt64: int64(amount),
			CashNum:      float64(amount),
			LoseUidS:     nil,
		})
		if err != nil {
			if strings.HasPrefix("UserCash_Insufficient_Uid_", err.Error()) {
				err = &PayError{
					message: "not enough money",
					Code:    CashNotEnough,
				}
				return
			}
			logger.Error("op balance fail, %s", err.Error())
			err = s.UpdateOrderStatus(orderId, TransFail)
			if err != nil {
				logger.Error("withdraw update order status fail, %s", err.Error())
			}
			return
		}
		err = &PayError{
			message: "api gateway response fail",
			Code:    createResp.StatusCode,
		}
	}
	return
}

//订单状态更新
func (s *service) UpdateOrderStatus(orderId int64, status string) (err error) {
	if _status, ok := orderProcessList[orderId]; ok && _status == status {
		logger.Info("order status not change, %s", err.Error())
	}
	ok, err := s.dao.LockOrderStatus(orderId)
	if err != nil{
		logger.Info("get order lock fail, %s", err.Error())
		err = &PayError{
			message: "order locked by other process",
			Code:    1004,
		}
		return
	}
	if !ok {
		logger.Info("order locked fail, orderId: %d", orderId)
		err = &PayError{
			message: "order locked by other process",
			Code:    1004,
		}
		return
	}
	orderInfo, err := s.dao.GetOrderById(orderId)
	if err != nil {
		logger.Info("get order info fail, %s", err.Error())
		err = &PayError{
			message: fmt.Sprintf("get order info fail"),
			Code:    1005,
		}
		return
	}
	err = s.dao.UpdateOrder(&dao.Transaction{
		OrderId: orderId,
		Status:  status,
	})
	if err != nil {
		logger.Error("update order fail, %s", err.Error())
	}
	if status == "Success" {
		balance, _err := GetBalanceService().BalanceNormal(&model.BalanceRequest{
			UidS:         []int64{orderInfo.Uid},
			Op:           model.Op_Plus,
			CashNumInt64: int64(orderInfo.Amount),
			CashNum:      float64(orderInfo.Amount),
		})
		if _err != nil {
			logger.Error("recharge fail, need check info, %s", err.Error())
			//TODO 订单状态暂时不便，需要手动干预用户数据
			err = &PayError{
				message: fmt.Sprintf("order balance handle fail, %s", err.Error()),
				Code:    1005,
			}
			return
		}
		logger.Info("recharge success, new balance %+v", balance)
	}
	if err := s.dao.ReleaseOrderStatus(orderId); err != nil {
		logger.Info("recharge success, but order lock release fail, %s", err.Error())
		err = nil
	}
	return
}

//订单状态查询，只查询本地状态信息
func (s *service) PayInOutStatus(orderId int64) {

}

func (s *service) GetOrderByUid(uid int64, offset, limit int) {

}

func (s *service) GetOrderList(uid int64) {

}

//订单状态异常处理
func (s *service) orderWatcher() {
	//TODO 待处理队列
	//TODO 状态查询
	s.getOrderStatus(0)
	//TODO 处理超时订单
	//TODO 处理异常订单
}

//生成订单ID
func (s *service) getOrderId() (orderId int64, err error) {
	var (
		resp *http.Response
		uuid map[string]int64
	)
	uuid = map[string]int64{}
	resp, err = s.uuidClient.Get(app.Conf.UUIDConf.URL)
	if err != nil {
		err = fmt.Errorf("uuid request fail, %s", err.Error())
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode == http.StatusOK {
		if err = json.NewDecoder(io.Reader(resp.Body)).Decode(&uuid); err != nil {
			err = fmt.Errorf("uuid decode fail, %s", err.Error())
			return
		}
		if _, ok := uuid["uuid"]; !ok {
			err = fmt.Errorf("uuid not found")
			return
		}
		orderId = uuid["uuid"]
	} else {
		err = fmt.Errorf("uuid resp code %d", resp.StatusCode)
		return
	}
	return
}

//从支付网关订单状态查询
func (s *service) getOrderStatus(orderId int64) (status string, err error) {
	var (
		resp      *http.Response
		payStatus *PayStatusResp
	)
	resp, err = s.uuidClient.Get(fmt.Sprintf("http://%s/", app.Conf.PayConf.Host))
	if err != nil {
		err = fmt.Errorf("pay status request fail, %s", err.Error())
		return
	}
	defer resp.Body.Close()
	if resp.StatusCode != http.StatusOK {
		err = fmt.Errorf("pay status code %d", resp.StatusCode)
		return
	}
	if err = json.NewDecoder(io.Reader(resp.Body)).Decode(&payStatus); err != nil {
		err = fmt.Errorf("pay status decode fail, %s", err.Error())
		return
	}
	if payStatus.Code != "200" {
		err = fmt.Errorf(" pay status query fail, %s", payStatus.Message)
		return
	}
	if payStatus.Data == nil {
		err = fmt.Errorf(" pay status query fail, data nil")
		return
	}
	switch payStatus.Data.Status {
	case PayFail:
		status = TransFail
	case PaySuccess:
		status = TransSuccess
	case PayIngress:
		status = TransInProgress
	default:
		status = TransUnknown
	}

	return
}
